1. 多线程的原理
线程是可并发执行的,拥有最小系统资源,共享进程资源的基本调度单位。
共有堆,自有栈 , iOS 主线程栈和其他线程均默认为 512K。
堆可以理解为操作系统提供的一块内存,可以随意使用,在我们使用 new , alloc
时,分配的内存就是系统用堆提供的。
栈是操作系统给当前的应用程序分配的,仅供当前应用程序使用的内存空间,也就是给我们的线程分配的内存空间,这个空间是有限制的,它主要是用来定义一些函数内部的成员变量,栈里的数据其他程序不能访问。
这也就是说,不要定义很大的局部变量,如果一个函数的成员变量,是一个数组,这个数组大于 512K,那么很可能程序根本不能运行。
在实际开发中,线程最好保持在 3-5 条,过多的线程会消耗大量 CPU 资源,单个线程被调度的执行频次会降低,因此线程并不是越多越好。
并发执行进度不可控,我们创建的线程何时执行是由操作系统调度,比如说我们有5个线程,每个线程打印一句话,那么在每次运行程序时,这5句话的打印顺序可能都不一样,这导致了两个潜在的风险。
对非原子操作容易造成状态不一致
- 这是说在我们使用多线程时,加入同一个变量进行访问,有的读,有的写,如果没有很好的控制机制的话,读到的值可能并不是需要的值。那么为了处理这种情况,我们会使用加锁控制,但这又会引起另一个风险。
加锁控制有死锁的风险
加锁控制是在读或写之前,通常是在写之前,首先给这个变量加一把锁,加锁以后其他进程无法读到,在写的操作完成之后,再把锁解开,其他的进程才可以进行读的操作。
这个风险在于,当其他进程也有锁时,当前进程需要读一个变量,读到之后才能进行写的操作,但是这个变量的锁需要等当前进程的锁打开,两个进程都在等对方解锁,这就触发了死锁的问题。
2. iOS中主要的多线程技术
技术方案 | 简介 | 语言 | 线程声明周期 |
---|---|---|---|
pthread | 一套通用的多线程API,跨平台可移植,使用难度大。 | C | 程序员管理 |
NSThread | 使用更加向对象,简单易用可直接操作线程对象。 | OC | 程序员管理 |
GCD | 旨在替代NSThread等线程技术,充分理由设备多核。 | C | 自动管理 |
NSOperation | 基于GCD,多了一些实用功能,使用更加面向对象。 | OC |
3. 多线程的使用
当我们在 UI 线程,也就是主线程中,执行一个耗时操作时,因为线程是串行的,所以会导致 UI 界面有无法刷新,无法响应等问题,例如我们使用一个 Button
,响应函数执行一个for
循环,那么在循环完成之前,Button
是灰色的。
|
|
这个例子告诉我们,不要把耗时操作放在主线程/UI线程中。
那么,如何解决这个问题呢?这就用到了我们的多线程技术。
- 引入
#import <pthread.h>
头文件,因为是系统文件,所以要使用尖括号引入。
- 在
BtnClick
方法里创建线程。
|
|
- 创建线程的
pthread_create()
方法的四个参数意义如下:
类型 | 功能 | 填写 |
---|---|---|
pthread_t * | 线程的id,线程创建后,可通过id操作线程。 | 传入自己声明的属性 |
const pthread_attr_t * | 返回线程的属性 ,一般不需要。 | 填NULL |
void ()(void *) | 函数指针, 这是线程需要执行的方法。 | 自己写一个方法 |
void * | 上一个参数线程方法的传入值 | 可以是NULL |
- 创建要线程的执行的方法,也就是
pthread_create()
的第三个参数:
|
|
- 验证是否有两个线程在运行:
|
|
|
|
- 打印结果:
|
|
可以看出,有两个线程,而且主线程的 number=1 , name=main
。
并且点击 Button
,for
循环进行的同时,Button
的状态并不受影响。
到这里我们就实现了一个简单的 pthread
线程。